package triangulation;

import static org.lwjgl.opengl.GL11.GL_COLOR_ARRAY;
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_FILL;
import static org.lwjgl.opengl.GL11.GL_FLOAT;
import static org.lwjgl.opengl.GL11.GL_FRONT_AND_BACK;
import static org.lwjgl.opengl.GL11.GL_LINES;
import static org.lwjgl.opengl.GL11.GL_NORMAL_ARRAY;
import static org.lwjgl.opengl.GL11.GL_TRIANGLES;
import static org.lwjgl.opengl.GL11.GL_VERTEX_ARRAY;
import static org.lwjgl.opengl.GL11.glClear;
import static org.lwjgl.opengl.GL11.glColor3f;
import static org.lwjgl.opengl.GL11.glColorPointer;
import static org.lwjgl.opengl.GL11.glDisableClientState;
import static org.lwjgl.opengl.GL11.glDrawArrays;
import static org.lwjgl.opengl.GL11.glEnableClientState;
import static org.lwjgl.opengl.GL11.glNormalPointer;
import static org.lwjgl.opengl.GL11.glPolygonMode;
import static org.lwjgl.opengl.GL11.glPopMatrix;
import static org.lwjgl.opengl.GL11.glPushMatrix;
import static org.lwjgl.opengl.GL11.glRotatef;
import static org.lwjgl.opengl.GL11.glScalef;
import static org.lwjgl.opengl.GL11.glTranslated;
import static org.lwjgl.opengl.GL11.glVertexPointer;
import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
import static org.lwjgl.opengl.GL15.glBindBuffer;
import static org.lwjgl.opengl.GL15.glBufferData;
import static org.lwjgl.opengl.GL15.glGenBuffers;

import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Iterator;

import org.lwjgl.BufferUtils;

import color.CLRConst;
import color.CLRConvert;

import computation.Geometry;

import delaunay_triangulation.Point_dt;
import delaunay_triangulation.Triangle_dt;

import util.GeoTriangle;
import util.GeoVertex;
import util.STLBReader;
import util.STLTriangle;

public class STLMesh
{
	private static final int TRIANGLE_SIZE = 3; // used to tell the VBO the number of vertices per face
	private static final int VERTEX_SIZE = 3; // used to tell the VBO the number of floats per vertex
	private static final int EDGE_SIZE = 2; // used to tell the VBO the number of vertices per edge
	
	public ArrayList<STLTriangle> triangles;
	
	private boolean _lightsEnabled = false;
	private boolean _renderToRender = false;
	
	public boolean updateComplete;
	public boolean updateGeometry; 
	public boolean updateColors;
	
	public boolean bShowNormals;
	
	public double dLaserStrike;
	public double dLaserDip;
	
	// bounds of the mesh
	public double xBoundMin, xBoundMax, yBoundMin, yBoundMax, zBoundMin, zBoundMax;
	public double getBoundX_Legnth() { return xBoundMax - xBoundMin; }
	public double getBoundY_Legnth() { return yBoundMax - yBoundMin; }
	public double getBoundZ_Legnth() { return zBoundMax - zBoundMin; }
	
	// rotation related variables and methods
	private float _xRot = 0.0f;
	private float _yRot = 0.0f;
	private float _zRot = 0.0f;
	public void rotate(float x, float y, float z)
	{
		_xRot += x;
		_yRot += y;
		_zRot += z;
	}
	
	public void reset_rotations()
	{
		_xRot = 0.0f;
		_yRot = 0.0f;
		_zRot = 0.0f;
	}
	
	public STLMesh()
	{
		triangles = null;
		
		_lightsEnabled = true;
		_renderToRender = false;
//		dLaserDip = 0.0;
//		dLaserStrike = 0.0;
		
		dLaserDip = -1;
		dLaserStrike = -1;
	}
	
	public boolean isLoaded() { return triangles != null && triangles.size() > 0; }
	
	// lights control
	public boolean isLightsEnabled() { return _lightsEnabled; }
	public void enableLights() { _lightsEnabled = true; }
	public void disableLights() { _lightsEnabled = false; }
	
	public void load()
	{
		triangles = STLBReader.read();
		
		updateComplete = false;
		updateGeometry = false;
		updateColors = false;
		
		bShowNormals = false;
		
		xBoundMin = yBoundMin = zBoundMin = Double.MAX_VALUE;
		xBoundMax = yBoundMax = zBoundMax = Double.MIN_VALUE;
		
		// determine the bounds of the mesh
		for (STLTriangle t: triangles)
		{
			double x1 = t.v1[0];
			double x2 = t.v2[0];
			double x3 = t.v3[0];
			
			double y1 = t.v1[1];
			double y2 = t.v2[1];
			double y3 = t.v3[1];
			
			double z1 = t.v1[2];
			double z2 = t.v2[2];
			double z3 = t.v3[2];
			
			xBoundMin = Math.min(xBoundMin, x1);
			xBoundMin = Math.min(xBoundMin, x2);
			xBoundMin = Math.min(xBoundMin, x3);
			
			yBoundMin = Math.min(yBoundMin, y1);
			yBoundMin = Math.min(yBoundMin, y2);
			yBoundMin = Math.min(yBoundMin, y3);
			
			zBoundMin = Math.min(zBoundMin, z1);
			zBoundMin = Math.min(zBoundMin, z2);
			zBoundMin = Math.min(zBoundMin, z3);

			xBoundMax = Math.max(xBoundMax, x1);
			xBoundMax = Math.max(xBoundMax, x2);
			xBoundMax = Math.max(xBoundMax, x3);
			
			yBoundMax = Math.max(yBoundMax, y1);
			yBoundMax = Math.max(yBoundMax, y2);
			yBoundMax = Math.max(yBoundMax, y3);
			
			zBoundMax = Math.max(zBoundMax, z1);
			zBoundMax = Math.max(zBoundMax, z2);
			zBoundMax = Math.max(zBoundMax, z3);
		}
		
		_renderToRender = false;
	}
	
	private int vboHandle_Vertices;
	private int vboHandle_Normals;
	private int vboHandle_NormalLines;
	private int vboHandle_Colors;
	private FloatBuffer buf_vertices;
	private FloatBuffer buf_normals;
	private FloatBuffer buf_normal_lines; // a buffer of lines representing all the normals per triangle
	private FloatBuffer buf_colors;
	// called once when loading a completely new geometry, needed for VBO rendering
	// returns true if geometry has been created
	public boolean createGeometry()
	{
		if (this.isLoaded() && !_renderToRender) // done once we have loaded a mesh
		{
			System.out.print("Creating initial OpenGL geometry ... ");
			final int numberTriangles = triangles.size();
			
			// create the buffers and maps
			buf_vertices = BufferUtils.createFloatBuffer(numberTriangles * TRIANGLE_SIZE * VERTEX_SIZE); // 3 floats per 1 vertex per 1 triangle
			buf_normals = BufferUtils.createFloatBuffer(numberTriangles * TRIANGLE_SIZE * VERTEX_SIZE); // 3 floats per 1 normal per 1 triangle
			buf_colors = BufferUtils.createFloatBuffer(numberTriangles * TRIANGLE_SIZE * VERTEX_SIZE); // 3 floats per 1 color per 1 vertex
			buf_normal_lines = BufferUtils.createFloatBuffer(numberTriangles * VERTEX_SIZE * EDGE_SIZE); // 3 floats per normal per triangle x2 as we want a line

			for (STLTriangle t : triangles)
			{
				// fill the vertex buffer
				float[] triangle = {t.v1[0], t.v1[1], t.v1[2],
									t.v2[0], t.v2[1], t.v2[2],
									t.v3[0], t.v3[1], t.v3[2]};
				
				buf_vertices.put(triangle);
				
				// fill the normal buffer, per vertex normals
				buf_normals.put(t.normal);
				buf_normals.put(t.normal);
				buf_normals.put(t.normal);
				
				
				// compute the normal lines and fill the normal lise buffer
				float[] triangleCentre = new float[3];
				triangleCentre[0] = (float)((t.v1[0] + t.v2[0] + t.v3[0])/3);
				triangleCentre[1] = (float)((t.v1[1] + t.v2[1] + t.v3[1])/3);
				triangleCentre[2] = (float)((t.v1[2] + t.v2[2] + t.v3[2])/3);
				buf_normal_lines.put(triangleCentre);
				float[] normal_line = Geometry.vectorPlus(triangleCentre, Geometry.vectorNormalize(t.normal));
				buf_normal_lines.put(normal_line);
				
				// color each of the faces
				double[] strike_n_dip = Geometry.computeStrikeNDipAngle2(t.normal, dLaserStrike, dLaserDip);
//				if (!Double.isNaN(strike_n_dip[1]) && !Double.isNaN(strike_n_dip[0]))
//				{
//					float[] clr = CLRConvert.GetStrikeDipColor(strike_n_dip[0], strike_n_dip[1]);
//					buf_colors.put(clr);
//					buf_colors.put(clr);
//					buf_colors.put(clr);
//				}
//				else
//				{
					// set the intial color to be green
					buf_colors.put(CLRConst.GREEN);
					buf_colors.put(CLRConst.GREEN);
					buf_colors.put(CLRConst.GREEN);						
//				}
			}
			
			buf_vertices.flip();
			buf_normals.flip();
			buf_colors.flip();
			buf_normal_lines.flip();

			vboHandle_Vertices = glGenBuffers();
			vboHandle_Normals = glGenBuffers();
			vboHandle_Colors = glGenBuffers();
			vboHandle_NormalLines = glGenBuffers(); // normal lines
			
	        glBindBuffer(GL_ARRAY_BUFFER, vboHandle_Vertices);
	        glBufferData(GL_ARRAY_BUFFER, buf_vertices, GL_STATIC_DRAW);
	        glBindBuffer(GL_ARRAY_BUFFER, vboHandle_Normals);
	        glBufferData(GL_ARRAY_BUFFER, buf_normals, GL_STATIC_DRAW);
	        glBindBuffer(GL_ARRAY_BUFFER, vboHandle_Colors);
	        glBufferData(GL_ARRAY_BUFFER, buf_colors, GL_STATIC_DRAW);
	        glBindBuffer(GL_ARRAY_BUFFER, vboHandle_NormalLines);
	        glBufferData(GL_ARRAY_BUFFER, buf_normal_lines, GL_STATIC_DRAW);
	        
	        glBindBuffer(GL_ARRAY_BUFFER, 0);
	        // compute any extra information here
	        
			_renderToRender = true;
			System.out.println(" done!");
			System.out.println("Number of Triangles: " + numberTriangles);
			return true;
		}
		return false;
	}
	
	public void render()
	{
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		
		// ensure we are ready to render before we alter the rendering data!
		if (_renderToRender)
		{
//			if (updateComplete)
//			{
//				regenerate_complete();
//				updateComplete = false;
//			}
//			
//			if (updateGeometry)
//			{
//				regenerate_geometry();
//				updateGeometry = false;
//			}
//			
//			if (updateColors)
//			{
//				repaint_triangulation();
//				updateColors = false;
//			}
		}

		glPushMatrix();
		
		float x = (float)(xBoundMin + getBoundX_Legnth()/2);
		float y = (float)(yBoundMin + getBoundY_Legnth()/2);
		float z = (float)(zBoundMin);
		
		glScalef(1.0f, 1.0f, 1.0f);
		glTranslated(x, y, z);
		glRotatef(_zRot, 0.0f, 0.0f, 1.0f);
		glRotatef(_yRot, 0.0f, 1.0f, 0.0f);
		glRotatef(_xRot, 1.0f, 0.0f, 0.0f);
		glTranslated(-x, -y, -z);
		
		render_mesh();
		
		glPopMatrix();
	}
		
	private void render_mesh()
	{
		// render using VBO
		if (_renderToRender)
		{
	        glClear(GL_COLOR_BUFFER_BIT);

            glBindBuffer(GL_ARRAY_BUFFER, vboHandle_Vertices);
            glVertexPointer(3, GL_FLOAT, 0, 0L); // vertex size = 3 => 3 floats per vertex
            glBindBuffer(GL_ARRAY_BUFFER, vboHandle_Normals);
            glNormalPointer(GL_FLOAT, 0, 0L);
            glBindBuffer(GL_ARRAY_BUFFER, vboHandle_Colors);
            glColorPointer(3, GL_FLOAT, 0, 0L); // color size = 3 => 3 floats per color
            
            glEnableClientState(GL_VERTEX_ARRAY);
            glEnableClientState(GL_NORMAL_ARRAY);
            glEnableClientState(GL_COLOR_ARRAY);
            
            glDrawArrays(GL_TRIANGLES, 0, triangles.size()*3);
            
            glDisableClientState(GL_COLOR_ARRAY);
            glDisableClientState(GL_NORMAL_ARRAY);
            glDisableClientState(GL_VERTEX_ARRAY);
            
            // display the vector normals
            if (bShowNormals)
            {            	
                glBindBuffer(GL_ARRAY_BUFFER, vboHandle_NormalLines);
                glVertexPointer(3, GL_FLOAT, 0, 0L); // vertex size = 3 => 3 floats per vertex
                
            	glColor3f(1.0f, 1.0f, 1.0f);
            	glEnableClientState(GL_VERTEX_ARRAY);
            	glDrawArrays(GL_LINES, 0, triangles.size()*2);
            	glDisableClientState(GL_VERTEX_ARRAY);
            }
            
            
//            glBindBuffer(GL_ARRAY_BUFFER, 0); // TODO: find out if this line is truly un-needed
		}	
	}
}
